<template>
  <div id="vr"></div>
</template>

<script>
import { itemList, lastAnime, KEYLIST, getItem, addCamera, addControls, refreshKeyAngle, loadAnime, euler2quat, quat2euler } from './util'


/**
 * Three.js 全局变量
 * @param { 场景 } scene
 * @param { 相机 } camera
 * @param { 控制器 } controls
 * @param { 渲染器 } renderer
 * @param { 动画混合器 } animationMixer
 * @param { 帧与帧间的时间 } clock
 * @param { 物理世界 } physic
 */
export let scene, camera, controls, renderer, animationMixer, clock, world, raycaster;

let SPEED_WALK = 0.055, SPEED_RUN = 2.5 * SPEED_WALK, CANVAS_WIDTH = 0, CANVAS_HEIGHT = 0; // 走路的速度. 跑步的速度

let isWalk = false, isRun = false, isJump = false, jumping = false; // 模型是否行走, 是否跑步， 是否跳跃

let angle = 0, additionalAngle = 0, velocity = 0.055, lastKey = '', JUMP_TIME = 550, JUMP_DOWN_TIME = 320; // 旋转的角度. 移动速度

let _this;


export default {
  name: 'VR',
  props: {
    disabled: {
      type: Boolean,
      default: true,
    }
  },
  data() {
    return {
      // 物体列表
      Objects: [],
      // 画布
      CANVAS: null,
    }
  },
  mounted() {

    this.init();
    this.eventListener();
    this.forceMouse();

  },
  methods: {
    async init() {

      _this = this;

      // 获取画布
      this.CANVAS = document.getElementById("vr");
      CANVAS_WIDTH = this.CANVAS.clientWidth, CANVAS_HEIGHT = this.CANVAS.clientHeight;

      // 创建场景
      scene = new THREE.Scene();

      // 渲染器渲染到画布
      renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
      renderer.setClearColor(new THREE.Color(0xEEEEEE));
      renderer.setSize(CANVAS_WIDTH, CANVAS_HEIGHT);
      this.CANVAS.appendChild(renderer.domElement);

      // 加载Cannon物理引擎
      world = new CANNON.World();
      world.quatNormalizeSkip = 0;
      world.quatNormalizeFast = false;
      world.gravity.set(0, -9.82, 0);
      world.broadphase = new CANNON.NaiveBroadphase(); // NaiveBroadphase是默认的碰撞检测方式，该碰撞检测速度比较高
      world.solver.iterations = 10; // 解算器的迭代次数，更高的迭代次数意味着更加精确同时性能将会降低
      world.solver.tolerance = 0.001;

      // 创建摄像机
      camera = addCamera(CANVAS_WIDTH / CANVAS_HEIGHT, { x: 0, y: 1.61 * 4, z: -1.61 * 4 * 1.22 })

      // 相机坐标轴辅助, 世界坐标轴辅助
      let helper = new THREE.CameraHelper(camera);
      let axesHelper = new THREE.AxesHelper(5);
      // scene.add(helper);
      // scene.add(axesHelper);

      // 世界坐标轴方格
      let grid = new THREE.GridHelper(1000, 1000);
      // scene.add(grid);

      // 射线
      raycaster = new THREE.Raycaster();

      // 光源: 开启阴影投射
      let light = new THREE.DirectionalLight(0xffffff, 0.3, 0);
      light.position.set(100, 100, 0);
      light.castShadow = true;
      scene.add(light);

      // 时钟
      clock = new THREE.Clock();

      // 动画混合器
      animationMixer = new THREE.AnimationMixer(scene);

      // 加载模型
      this.initModel();

      // 控制交互
      controls = addControls(camera, renderer.domElement, {
        enabled: true,
        rotateSpeed: .5, // 旋转速度
        zoomSpeed: 2, // 变焦速度
        panSpeed: 2, // 平移速度
        noZoom: false, // 是否不变焦
        noPan: false, // 是否不平移
        staticMoving: true, // 是否开启移动惯性
        dynamicDampingFactor: 0.8, // 动态阻尼系数 灵敏度
        enablePan: false, // 是否开启右键拖拽
      })

      const render = () => {

        // 更新控制器
        controls.update();

        let time = clock.getDelta();
        // 更新物模型
        if (getItem(`robot1`)) {
          let robot = getItem(`robot1`)
          // 跳跃
          if (jumping) {
            // 跳跃高度 H = [(JUMP_DOWN_TIME / 1000)^ 2] * 9.82 / 2, 单位跳跃时间 T = H / (JUMP_TIME - JUMP_DOWN_TIME)
            robot.phy.position.y += 0.08;
          }
          // 模型的欧拉角转四元数 根据模型 更新其物理盒子的四元数、位置
          let { x, y, z } = quat2euler(robot.phy.quaternion)
          robot.api.scene.rotation.x = x;
          robot.api.scene.rotation.y = y;
          robot.api.scene.rotation.z = z;
          robot.api.scene.position.x = robot.phy.position.x;
          // 注意CANNON和THREE的模型position引起的差异
          robot.api.scene.position.y = robot.phy.position.y - robot.phy.half_y;
          robot.api.scene.position.z = robot.phy.position.z;
          world.step(1 / 60);
          // 控制器的视角锁定中心
          let vector = new THREE.Vector3().copy(robot.phy.position)
          controls.target = vector;
        }

        // 更新动画
        animationMixer.update(time);
        if (isWalk) {
          this.itemMove(`robot1`);
        }

        // 将更新的内容重新渲染
        renderer.render(scene, camera);
        requestAnimationFrame(render);

      }

      // 监听冒泡阶段的resize事件
      window.addEventListener('resize', this.onWindowResize, false)
      render();

    },

    // 窗口大小改变的回调函数
    onWindowResize() {

      let CANVAS_WIDTH = this.CANVAS.clientWidth, CANVAS_HEIGHT = this.CANVAS.clientHeight;
      // 更新相机视角
      camera.aspect = CANVAS_WIDTH / CANVAS_HEIGHT;
      camera.updateProjectionMatrix();
      // 重新设置渲染器的大小
      renderer.setSize(CANVAS_WIDTH, CANVAS_HEIGHT);
      renderer.render(scene, camera);

    },

    // 手势识别回调函数
    hands_acitons(key, side) {
      if (key === null && side === 'Right') {
        for (let action in KEYLIST) {
          _this.keyUpActions(action);
        }
        return
      }
      else {
        _this.keyDownActions(key);
        if (side === 'Right') {
          for (let action in KEYLIST) {
            if (action !== key && action !== 'shift' && action !== ' ') _this.keyUpActions(action);
          }
        } else {
          for (let action in KEYLIST) {
            if (action !== key && action === 'shift' && action === ' ') _this.keyUpActions(action);
          }
        }
      }
    },

    // 在3D场景中监听事件, 监听document
    eventListener() {

      // 监听键盘按下的事件
      document.addEventListener('keydown', ev => {

        this.keyDownActions(ev.key.toLowerCase())

      }, false)

      // 监听键盘弹起的事件
      document.addEventListener('keyup', ev => {

        this.keyUpActions(ev.key.toLowerCase())

      }, false)


      // 若暂离本页面, 停止操作, 并初始化 KEYLIST
      document.addEventListener('visibilitychange', () => {

        // 页面变为不可见时触发 
        if (document.visibilityState == 'hidden') { }
        // 页面变为可见时触发 
        if (document.visibilityState == 'visible') { }
        velocity = SPEED_WALK;
        isWalk = false;
        KEYLIST.a = KEYLIST.w = KEYLIST.d = KEYLIST.s = false;

      }, true);

      let is_click = false;
      // 点击事件
      document.addEventListener('click', (ev) => {
        if (!is_click && !isWalk) {
          is_click = true
          let mouse = new THREE.Vector2();
          mouse.x = (ev.clientX / CANVAS_WIDTH) * 2 - 1;
          mouse.y = - (ev.clientY / CANVAS_HEIGHT) * 2 + 1;
          raycaster.setFromCamera(mouse, camera);
          let intersects = raycaster.intersectObjects(scene.children[1].children);
          if (intersects.length !== 0) {
            loadAnime(animationMixer, `robot1`, `Yes`, 1, false);
            setTimeout(() => {
              animationMixer.uncacheRoot(animationMixer.getRoot());
            }, 1000)
          }
          setTimeout(() => {
            is_click = false
          }, 2000);
        }
      }, false);

    },

    // 相机绑定物体的移动
    itemMove(item) {

      let model = getItem(item);
      angle = controls.getAzimuthalAngle();
      let result = refreshKeyAngle(lastKey);
      if (result !== null) additionalAngle = result;
      let X_INCREASE = velocity * Math.sin(angle + additionalAngle);
      let Z_INCREASE = velocity * Math.cos(angle + additionalAngle);
      camera.position.x -= X_INCREASE;
      camera.position.z -= Z_INCREASE;

      // 模型的位置 转动角度
      model.phy.position.x -= X_INCREASE;
      model.phy.position.z -= Z_INCREASE;
      model.api.scene.rotation._order = `XYZ`;
      let angleY = angle + additionalAngle;
      let quaternion = euler2quat({ x: Math.PI, y: angleY, z: Math.PI });
      model.phy.quaternion.copy(quaternion);

    },

    /**
     * 强制鼠标左键点击-模拟按下动作
     * 使用户进入后可以直接通过鼠标移动来控制视角
     * 由于无法禁止用户的点击行为, 在用户点击时重新模拟按下动作
     */
    forceMouse(clickX = this.CANVAS.clientWidth / 2, clickY = this.CANVAS.clientHeight / 2) {

      if (!this.CANVAS.is_set) {
        this.CANVAS.addEventListener("click", callBack, false);
        // 闭包
        let that = this;
        function callBack(ev) {
          return that.forceMouse(ev.clientX, ev.clientY)
        }
        this.CANVAS.is_set = true;
      }

      event = document.createEvent('MouseEvent');
      // 事件id
      event.pointerId = 1;
      event.pointerType = 'mousedown'
      event.initMouseEvent('pointerdown', true, true, window, 0, 0, 0, clickX, clickY);
      renderer.domElement.dispatchEvent(event);

    },

    // 模型加载 初始化
    initModel() {

      let loader = new THREE.GLTFLoader()

      // 机器人模型
      loader.load('/static/RobotExpressive.glb', (geometry) => {
        geometry.scene.traverse(function (child) {
          //放射光颜色与放射光贴图 不设置可能导致黑模
          if (child.isMesh) {
            child.material.emissive = child.material.color;
            child.material.emissiveMap = child.material.map;
          }
        });
        geometry.scene.add(new THREE.AxesHelper(3));
        const scale = { x: 0.42, y: 0.42, z: 0.42 }; // 2.016m Height
        let boxInfo = new THREE.Box3().setFromObject(geometry.scene);
        let { x, y, z } = boxInfo.getSize();
        let size_x = x * scale.x / 2, size_y = y * scale.y / 2, size_z = z * scale.z / 2;

        geometry.scene.scale.copy(scale);
        geometry.scene.position.set(0, 0, 0);
        geometry.receiveShadow = true;
        geometry.castShadow = true;

        let box = new CANNON.Body({
          mass: 50,
          position: new CANNON.Vec3(0, size_y, 0),
          shape: new CANNON.Box(new CANNON.Vec3(size_x, size_y, size_z)),
          material: new CANNON.Material({ friction: .1, restitution: 0 }) //材质数据，里面规定了摩擦系数和弹性系数
        });

        /**
         * 创建物理盒子, 将模型放入盒子,mass:重量(Kg) shape: 形状
         * 在每次render渲染时都将更新box与模型的位置和四元数, 使其
         * 相同, 盒子具有物理属性, 而模型随之变化, 因此之后的操作除
         * 了动画以外都是操作box
         */
        box.half_y = size_y;
        box.quaternion.copy(geometry.scene.quaternion);
        world.addBody(box); //将创建好的立方体加入到世界中
        itemList.push({ type: 'robot1', api: geometry, phy: box });
        scene.add(geometry.scene);

      }, function (xhr) {
        console.log(`模型加载: ${(xhr.loaded / xhr.total * 100)}%`);
      }, function (error) {
        console.log(error);
      })

      // 环境模型
      loader.load('/static/collision-world.glb', (geometry) => {
        geometry.scene.traverse(function (child) {
          if (child.isMesh) {
            child.material.emissive = child.material.color;
            child.material.emissiveMap = child.material.map;
          }
        });
        const scale = { x: 3, y: 3, z: 3 };

        geometry.scene.scale.copy(scale);
        geometry.scene.position.set(0, 5.2125, 0);
        geometry.receiveShadow = true;
        geometry.castShadow = true;

        let box = new CANNON.Body({
          mass: 0,
          shape: new CANNON.Plane(),
          material: new CANNON.Material({ friction: .05, restitution: 0 })
        });

        box.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
        world.addBody(box);
        itemList.push({ type: 'env', api: geometry, phy: box });
        scene.add(geometry.scene);

      }, function (xhr) { }, function (error) { })

    },

    // 按键按下回调函数
    keyDownActions(action) {
      if (this.disabled === false) {
        // 如果正在跳跃则按键无效
        if (isJump) return
        if (KEYLIST[action] !== undefined) {
          // 使对应的按键状态为 true
          KEYLIST[action] = true;
          if (refreshKeyAngle(action) === null) {
            switch (action) {
              case 'shift':  // 冲刺
                // 未在Run状态并且有方向键被激活时启动Run动画, 改变速度(频繁调用会使得动画刷新)
                if (!isRun && (KEYLIST.a || KEYLIST.w || KEYLIST.s || KEYLIST.d)) {
                  loadAnime(animationMixer, `robot1`, `Running`)
                  velocity = SPEED_RUN;
                  isRun = true;
                }
                return
              case ' ':  // 跳跃
                //  跳跃时不可操作其它动作
                if (!isJump) {
                  isJump = true;
                  loadAnime(animationMixer, `robot1`, `WalkJump`, JUMP_TIME / 1000, false)
                  jumping = true;
                  setTimeout(function () {
                    jumping = false;
                    // 如果停止跳跃后没有方向键使能, 则停止
                    setTimeout(() => {
                      if (!KEYLIST.w && !KEYLIST.a && !KEYLIST.s && !KEYLIST.d) {
                        animationMixer.uncacheRoot(animationMixer.getRoot());
                        isWalk = false;
                      } else loadAnime(animationMixer, `robot1`, lastAnime);
                      isJump = false
                    }, JUMP_DOWN_TIME) // 下跳(落)的时间
                  }, JUMP_TIME - JUMP_DOWN_TIME) // 往上跳的时间
                }
                return
              default: break
            }
          }
          // 记录上次的按键
          lastKey = action;
          // 加载走路状态
          if (!isWalk) {
            loadAnime(animationMixer, `robot1`, `Walking`);
            velocity = SPEED_WALK;
            isWalk = true;
          }
        }
      }
    },

    // 按键弹起回调函数
    keyUpActions(action) {

      if (KEYLIST[action] !== undefined) {
        // 使对应的按键状态为 false
        KEYLIST[action] = false;
        switch (action) {
          case 'shift':  // 冲刺
            if (isRun && (KEYLIST.a || KEYLIST.w || KEYLIST.s || KEYLIST.d)) {
              loadAnime(animationMixer, `robot1`, `Walking`)
              velocity = SPEED_WALK;
              isRun = false;
            }
            return
          case ' ':  // 跳跃
            break;
          default: break
        }
        // 如果全部的状态都为 false 则 移除走路状态
        if (!KEYLIST.a && !KEYLIST.w && !KEYLIST.d && !KEYLIST.s) {
          if (!isJump) animationMixer.uncacheRoot(animationMixer.getRoot());
          isWalk = false;
        }
      }

    }
  }
}
</script>

<style scoped lang="scss">
#vr {
  width: 100%;
  height: 100%;
  color: white;
}
</style>
